// Copyright (c) 2020 Presto Labs Pte. Ltd.
// Author: jshin

#pragma once

#include <string>

#include <rapidjson/document.h>
#include <rapidjson/rapidjson.h>

#include "coin/proto/coin_market_enums.pb.h"
#include "coin2/base/conversion.h"
#include "coin2/exchange/base/feed/update.h"
#include "coin2/exchange/base/feed_util/book_builder.h"
#include "coin2/exchange/base/feed_util/trade.h"

namespace impl {
using coin2::exchange::base::market::ExchangeType;
using coin2::exchange::base::market::MarketType;

class KrakenFeedUpdateParser {
 public:
  static bool ParseBook(BookBuilder* book_builder_, const rapidjson::Value& book_json) {
    if (book_json.HasMember("bs") && book_json.HasMember("as")) {
      // snapshot may be empty
      // [810,{"as":[],"bs":[]},"book-25","XRP/USD"]
      std::string ts_str;
      if (!book_json["bs"].GetArray().Empty()) {
        ts_str = book_json["bs"].GetArray()[0].GetArray()[2].GetString();
      } else if (!book_json["as"].GetArray().Empty()) {
        ts_str = book_json["as"].GetArray()[0].GetArray()[2].GetString();
      } else {
        return false;
      }

      auto ts = std::llround(stod(ts_str.c_str()) * 1'000'000'000LL);
      auto tx = book_builder_->NewTransaction(ts);
      tx.Clear();
      for (const auto& pq : book_json["bs"].GetArray()) {
        tx.UpdateBid(stod(pq[0].GetString()), stod(pq[1].GetString()));
      }
      for (const auto& pq : book_json["as"].GetArray()) {
        tx.UpdateAsk(stod(pq[0].GetString()), stod(pq[1].GetString()));
      }
    }
    if (book_json.HasMember("b")) {
      auto ts = std::llround(
          stod(book_json["b"].GetArray()[0].GetArray()[2].GetString()) * 1'000'000'000LL);
      auto tx = book_builder_->NewTransaction(ts);
      for (const auto& pq : book_json["b"].GetArray()) {
        tx.UpdateBid(stod(pq[0].GetString()), stod(pq[1].GetString()));
      }
    }
    if (book_json.HasMember("a")) {
      auto ts = std::llround(
          stod(book_json["a"].GetArray()[0].GetArray()[2].GetString()) * 1'000'000'000LL);
      auto tx = book_builder_->NewTransaction(ts);
      for (const auto& pq : book_json["a"].GetArray()) {
        tx.UpdateAsk(stod(pq[0].GetString()), stod(pq[1].GetString()));
      }
    }

    return true;
  }
  static void ParseTrade(const rapidjson::Value& trade_json, Trade* trade) {
    const auto& items = trade_json.GetArray();
    trade->price = stod(items[0].GetString());
    trade->fill_qty = stod(items[1].GetString());
    trade->side = ParseTradeSide(items[3].GetString());
    trade->timestamp = std::llround(stod(items[2].GetString()) * 1'000'000'000LL);
  }

  static coin2::exchange::feed::TradeSide ParseTradeSide(const std::string& side) {
    if (side == "b") {
      return coin2::exchange::feed::TRADE_BUY_SIDE;
    } else if (side == "s") {
      return coin2::exchange::feed::TRADE_SELL_SIDE;
    } else {
      throw std::runtime_error("[Kraken] unknown side: " + side);
    }
  }

  static fastfeed::proto::KlineInterval ParseKlineInterval(const std::string& interval) {
    static std::map<std::string, fastfeed::proto::KlineInterval> interval_map = {
      {"1", fastfeed::proto::KLINE_INTERVAL_1MIN},
      {"3", fastfeed::proto::KLINE_INTERVAL_3MIN},
      {"5", fastfeed::proto::KLINE_INTERVAL_5MIN},
      {"15", fastfeed::proto::KLINE_INTERVAL_15MIN},
      {"30", fastfeed::proto::KLINE_INTERVAL_30MIN},
      {"60", fastfeed::proto::KLINE_INTERVAL_1HOUR},
      {"1440", fastfeed::proto::KLINE_INTERVAL_1DAY},
    };
    auto iter = interval_map.find(interval);
    if (iter == interval_map.end()) {
      
      return fastfeed::proto::KLINE_INTERVAL_INVALID;
    }

    return iter->second;
  }

  static bool Parse1Kline(
      const rapidjson::Value& kline_data,
      Klines* klines,
      int64_t timestamp,
      fastfeed::proto::KlineInterval interval) {
    CHECK_EQ(kline_data.Size(), 8);
    int64_t open_timestamp = stoll(kline_data[0].GetString()) * 1'000'000'000LL;
    auto result = klines->AddKline(open_timestamp);
    if (!result) return false;

    Kline* kline;
    bool freezed;
    std::tie(kline, freezed) = result.value();

    kline->open = stod(kline_data[1].GetString());
    kline->high = stod(kline_data[2].GetString());
    kline->low = stod(kline_data[3].GetString());
    kline->close = stod(kline_data[4].GetString());
    kline->volume = stod(kline_data[6].GetString());
    kline->timestamp = timestamp;
    kline->interval = interval;
    return freezed;
  }
};
}  // namespace impl

using impl::KrakenFeedUpdateParser;
